#include "StdAfx.h"


#ifndef EXCLUDE_SCALEFORM_SDK

#include "GFxVideoWrapper.h"

#if defined(USE_GFX_VIDEO)

#include "SharedStates.h"
#include "GTextureXRender.h"
#include <GFxLoader.h>
#if defined(WIN32) || defined(WIN64) || defined(gringo)
#	include <GFxVideoPC.h>




#endif
#include "GFxVideoSoundCrySound.h"


static float s_sys_flash_video_buffertime = 2.0f;




static float s_sys_flash_video_buffertime_loading = 2.0f;
static float s_sys_flash_video_buffertime_startup = 7.5f;



//////////////////////////////////////////////////////////////////////////
// event listener for the GFxVideoPlayer wrapper below

class CryGFxVideoPlayer_SystemEventListener : public ISystemEventListener
{
public:
	enum EState
	{
		eState_Startup,
		eState_Loading,
		eState_InGame
	};

	// ISystemEventListener interface
public:
	virtual void OnSystemEvent(ESystemEvent event, UINT_PTR /*wparam*/, UINT_PTR /*lparam*/)
	{
		switch (event)
		{
		case ESYSTEM_EVENT_GAME_POST_INIT_DONE:
			m_eLoadingState = eState_InGame;
			break;
		case ESYSTEM_EVENT_LEVEL_LOAD_PREPARE:
			m_eLoadingState = eState_Loading;
			break;
		case ESYSTEM_EVENT_LEVEL_PRECACHE_END:
			m_eLoadingState = eState_InGame;
			break;
		default:
			break;
		}
	}

public:
	static void Register()
	{
		static bool s_registered = false;
		if (!s_registered)
		{
			gEnv->pSystem->GetISystemEventDispatcher()->RegisterListener(&ms_inst);
			s_registered = true;
		}
	}

	static CryGFxVideoPlayer_SystemEventListener& GetAccess()
	{
		return ms_inst;
	}

public:
	EState GetLoadingState() const
	{
		return m_eLoadingState;
	}

	float GetBufferTime() const
	{
		switch (m_eLoadingState)
		{
		case eState_Startup: return s_sys_flash_video_buffertime_startup;
		case eState_Loading: return s_sys_flash_video_buffertime_loading;
		case eState_InGame: return s_sys_flash_video_buffertime;
		}

		return s_sys_flash_video_buffertime;
	}

private:
	static CryGFxVideoPlayer_SystemEventListener ms_inst;

private:
	CryGFxVideoPlayer_SystemEventListener()
	: m_eLoadingState(eState_Startup)
	{
	}

	virtual ~CryGFxVideoPlayer_SystemEventListener()
	{
	}

private:
	EState m_eLoadingState;
};

CryGFxVideoPlayer_SystemEventListener CryGFxVideoPlayer_SystemEventListener::ms_inst;


//////////////////////////////////////////////////////////////////////////
// GFxVideoPlayer wrapper

class CryGFxVideoPlayer : public GFxVideoPlayer
{
	virtual void Destroy()
	{
		m_pPlayer->Destroy();
		delete this;
	}

	bool DetectAudioVideoMismatch(const char* url) const
	{
		CryGFxLog& log = CryGFxLog::GetAccess();

		bool mismatch = false;

		VideoInfo vInfo;
		if (LoadVideoInfo(url, &vInfo, &CryGFxFileOpener::GetAccess()))
		{
			const float videoTime = vInfo.TotalFrames / ((float) vInfo.FrameRate / 1000.0f);

			for (size_t i=0; i<vInfo.AudioTracks.GetSize(); ++i)
			{
				assert(vInfo.AudioTracks[i].SampleRate > 0);
				const float audioTime = vInfo.AudioTracks[i].TotalSamples / (float) vInfo.AudioTracks[i].SampleRate;
				if (fabsf(audioTime - videoTime) > 1.0f)
				{
					log.LogWarning("Length of channel %2d in audio stream : %.2f seconds", vInfo.AudioTracks[i].Index, audioTime);
					mismatch = true;
				}
			}

			if (mismatch)
			{
				log.LogWarning("Length of video stream               : %.2f seconds", videoTime);
				log.LogError("Length of audio and video streams mismatch. Video \"%s\" will not be played!", url);
			}
		}

		return mismatch;
	}

	virtual void Open(const char* url)
	{
		m_avMismatch = DetectAudioVideoMismatch(url);
		m_isStereoVideo = IsStereoVideoFile(url);
		m_pPlayer->Open(url);
#if defined ENABLE_FLASH_INFO
		m_videoFilePath = url;
		m_pPlayer->RenameVideoHeap(m_videoFilePath.ToCStr());
#endif
	}

	virtual Status GetStatus()
	{
		return !m_avMismatch ? m_pPlayer->GetStatus() : Finished;
	}
	virtual void GetVideoInfo(VideoInfo* info)
	{
		m_pPlayer->GetVideoInfo(info);
	}
	virtual void Play()
	{
		m_pPlayer->Play();
	}
	virtual void Seek(UInt32 pos)
	{
		m_pPlayer->Seek(pos);
	}
	virtual void SetSubtitleChannel(SInt channel)
	{
		m_pPlayer->SetSubtitleChannel(channel);
	}
	virtual SInt GetSubtitleChannel()
	{
		return m_pPlayer->GetSubtitleChannel();
	}
	virtual UInt32 GetPosition()
	{
		return m_pPlayer->GetPosition();
	}

	virtual GTexture* CreateTexture(GRenderer* pRenderer, SInt* texture_width, SInt* texture_height)
	{
		GTexture* pTex = m_pPlayer->CreateTexture(pRenderer, texture_width, texture_height);
		if (pTex)
		{
			GTextureXRenderBase* pTexBase = static_cast<GTextureXRenderBase*>(pTex);
			assert(pTexBase->IsYUV());
			if (pTexBase->IsYUV())
			{
				GTextureXRenderYUV* pTexYUV = static_cast<GTextureXRenderYUV*>(pTex);
				pTexYUV->SetStereoContent(m_isStereoVideo);
			}
		}
		return pTex;
	}

	virtual void UpdateTexture(GTexture* pRenderer, char* subtitle_msg, SInt subtitle_mgs_len)
	{
		m_pPlayer->UpdateTexture(pRenderer, subtitle_msg, subtitle_mgs_len);
	}
	virtual bool IsTextureReady()
	{
		return m_pPlayer->IsTextureReady();
	}
	virtual void Decode()
	{
		MEMSTAT_CONTEXT_FMT(EMemStatContextTypes::MSC_Other, 0, "Video Decode %s", m_videoFilePath.ToCStr());
		m_pPlayer->Decode();
	}
	virtual void Stop()
	{
		m_pPlayer->Stop();
	}
	virtual void Pause(bool on_off)
	{
		m_pPlayer->Pause(on_off);
	}
	virtual void SetSyncObject(SyncObject* pSyncObj)
	{
		m_pPlayer->SetSyncObject(pSyncObj);
	}
	virtual void GetCurrentCuePoints(GArray<CuePoint>* cue_points)
	{
		m_pPlayer->GetCurrentCuePoints(cue_points);
	}
	virtual void SetAudioTrack(SInt track_index)
	{
		m_pPlayer->SetAudioTrack(track_index);
	}
	virtual void SetSubAudioTrack(SInt track_index)
	{
		m_pPlayer->SetSubAudioTrack(track_index);
	}
	virtual void ReplaceCenterVoice(SInt track_index)
	{
		m_pPlayer->ReplaceCenterVoice(track_index);
	}
	virtual void SetLoopFlag(bool flag)
	{
		m_pPlayer->SetLoopFlag(flag);
	}

	virtual void SetBufferTime(Float time)
	{
		time = CryGFxVideoPlayer_SystemEventListener::GetAccess().GetBufferTime();
		if (time < 0.5f)
			time = 0.5f;
		m_pPlayer->SetBufferTime(time);
	}

	virtual void SetNumberOfFramePools(UInt pools)
	{
		pools = 1;
		m_pPlayer->SetNumberOfFramePools(pools);
	}

	virtual void SetReloadThresholdTime(Float time)
	{
		time = CryGFxVideoPlayer_SystemEventListener::GetAccess().GetBufferTime();
		time /= 2.5f;
		if (time < 0.25f)
			time = 0.25f;
		m_pPlayer->SetReloadThresholdTime(time);
	}
	virtual void GetReadBufferInfo(ReadBufferInfo* info)
	{
		m_pPlayer->GetReadBufferInfo(info);
	}
	virtual void SetDecodeHeaderTimeout(Float timeout)
	{
		m_pPlayer->SetDecodeHeaderTimeout(timeout);
	}
	virtual void RenameVideoHeap(const char* pName)
	{
		m_pPlayer->RenameVideoHeap(pName);
	}

public:
	CryGFxVideoPlayer(GFxVideoPlayer* pPlayer)
	: m_pPlayer(pPlayer)
	, m_isStereoVideo(false)
	, m_avMismatch(false)
#if defined ENABLE_FLASH_INFO
	, m_videoFilePath()
#endif
	{
		assert(m_pPlayer);
		CryInterlockedIncrement(&ms_numInstances);
	}
	virtual ~CryGFxVideoPlayer()
	{
		CryInterlockedDecrement(&ms_numInstances);
	}

	static int GetNumInstances()
	{
		return ms_numInstances;
	}

protected:
	static volatile int ms_numInstances;

protected:
	GFxVideoPlayer* m_pPlayer;
	bool m_isStereoVideo;
	bool m_avMismatch;
#if defined ENABLE_FLASH_INFO
	GString m_videoFilePath;
#endif
};

volatile int CryGFxVideoPlayer::ms_numInstances = 0;


//////////////////////////////////////////////////////////////////////////
// platform specific GFxVideo wrappers

#if defined(WIN32) || defined(WIN64) || defined(gringo)
class CryGFxVideoPC : public GFxVideoPC
{
public:
	CryGFxVideoPC(GThread::ThreadPriority decodingThreadsPriority, int decodingThreadsNumber, UInt32* affinityMask)
	: GFxVideoPC(decodingThreadsPriority, decodingThreadsNumber, affinityMask)
	{
	}

	virtual GFxVideoPlayer* CreateVideoPlayer(GMemoryHeap* pHeap, GFxTaskManager* pTaskManager, GFxFileOpenerBase* pFileOpener, GFxLog* pLog)
	{
		CryGFxVideoPlayer* pPlayerWrapped = 0;
		GFxVideoPlayer* pPlayer = GFxVideoPC::CreateVideoPlayer(pHeap, pTaskManager, pFileOpener, pLog);
		if (pPlayer)
			pPlayerWrapped = new CryGFxVideoPlayer(pPlayer);
		return pPlayerWrapped;
	}
};



















































#endif


void GFxVideoWrapper::SetVideo(GFxLoader* pLoader)
{
	assert(pLoader);

	GPtr<GFxVideo> pvc = 0;
	GPtr<GFxVideoSoundSystem> pvs = 0;

#if defined(WIN32) || defined(WIN64) || defined(gringo)
	UInt32 affinities[] = {DEFAULT_VIDEO_DECODING_AFFINITY_MASK, DEFAULT_VIDEO_DECODING_AFFINITY_MASK};
	pvc = *new CryGFxVideoPC(GThread::NormalPriority, sizeof(affinities) / sizeof(affinities[0]), affinities);






#endif

	if (pvc)
	{
		pvs = *new GFxVideoCrySoundSystem(pvc->GetHeap());
		pvc->SetSoundSystem(pvs);
	}
	pLoader->SetVideo(pvc);

	CryGFxVideoPlayer_SystemEventListener::Register();
}


int GFxVideoWrapper::GetNumVideoInstances()
{
	return CryGFxVideoPlayer::GetNumInstances();
}


void GFxVideoWrapper::InitCVars()
{
#if defined(_DEBUG)
	{
		static bool s_init = false;
		assert(!s_init);
		s_init = true;
	}
#endif

	REGISTER_CVAR2("sys_flash_video_buffertime", &s_sys_flash_video_buffertime, s_sys_flash_video_buffertime, VF_NULL, "Sets buffer time for videos (in seconds)");
	REGISTER_CVAR2("sys_flash_video_buffertime_loading", &s_sys_flash_video_buffertime_loading, s_sys_flash_video_buffertime_loading, VF_NULL, "Sets buffer time for videos during loading (in seconds)");
	REGISTER_CVAR2("sys_flash_video_buffertime_startup", &s_sys_flash_video_buffertime_startup, s_sys_flash_video_buffertime_startup, VF_NULL, "Sets buffer time for videos during startup (in seconds)");
}

#endif // #if defined(USE_GFX_VIDEO)


#endif //#ifndef EXCLUDE_SCALEFORM_SDK
